/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>

#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}
void SqlEngine::printTuple(int attr,int key, const string& value){
	
		switch (attr) {
			case 1:  // SELECT key
				fprintf(stdout, "%d\n", key);
				break;
			case 2:  // SELECT value
				fprintf(stdout, "%s\n", value.c_str());
				break;
			case 3:  // SELECT *
				fprintf(stdout, "%d '%s'\n", key, value.c_str());
				break;
		}
}
bool SqlEngine::evalCond(int key, const string& value, const std::vector<SelCond>& cond){
	int diff;
	
	for (unsigned i = 0; i < cond.size(); i++) {
      // compute the difference between the tuple value and the condition value
      switch (cond[i].attr) {
      case 1:
		diff = key - atoi(cond[i].value);
		break;
      case 2:
		diff = strcmp(value.c_str(), cond[i].value);
		break;
      }

      // skip the tuple if any condition is not met
      switch (cond[i].comp) {
      case SelCond::EQ:
		if (diff != 0) return false;
		break;
      case SelCond::NE:
		if (diff == 0) return false;
		break;
      case SelCond::GT:
		if (diff <= 0) return false;
		break;
      case SelCond::LT:
		if (diff >= 0) return false;
		break;
      case SelCond::GE:
		if (diff < 0) return false;
		break;
      case SelCond::LE:
		if (diff > 0) return false;
		break;
      }
    }
	return true;
}


RC SqlEngine::select_index(int attr, const string& table, const vector<SelCond>& conds, BTreeIndex& ind, RecordFile& rf){
	RC rc;
	
	//determine a starting, ending position from a list of conditions
	//we need to find the earliest starting position.
	//for example:
	//key<1 && key > 0 
	int min;
	int max;
	int count=0;
	RC eof; 
	RecordId rid;
	int key;
			
	string value;
	bool check=false;
	bool breakFalse=false;
	bool requireRecord = false;
	max = -1;
	min = 0;
	
	for(int i=0; i < conds.size(); i++){
		if(conds[i].attr == 1){
			switch(conds[i].comp)
			{
				case SelCond::LT:
					max = (max < atoi(conds[i].value)-1  && max != -1? max : atoi(conds[i].value)-1 );
					break;
				case SelCond::LE:
					max = (max < atoi(conds[i].value)  && max != -1? max : atoi(conds[i].value));
					break;
				case SelCond::GT:
					min = (min > atoi(conds[i].value)+1 ? min : atoi(conds[i].value)+1 );
					break;
				case SelCond::GE:
					min = (min > atoi(conds[i].value)  ? max : atoi(conds[i].value) );
					break;
				case SelCond::EQ:
					min = (min > atoi(conds[i].value)+1  ? min : atoi(conds[i].value) );
					check = true;
					breakFalse = true;
					break;
				case SelCond::NE:
					min=0;
					max=-1;
					check = true;
					breakFalse = false;
					break;
			}
		}
		else
		{
			requireRecord = true;
			check=true;
		}
	}
	if(attr != 1 && attr != 4)
		requireRecord = true;
	
	IndexCursor start;
	IndexCursor end;
	
	if(ind.locate(min,start) < 0)
		return 0;
	
	if(ind.locate(max,end) < 0)
		max = -1;
	
	//key only,
	
	if(requireRecord == false)
	{
		bool runagain;
		
		eof = ind.readForward(start, key, rid);
		do{
			
			if(check==false || evalCond(key,"",conds))
			{
				printTuple(attr,key,"");
				count++;
			}
			else{
				if(breakFalse)
					goto FINISH;
			}
			
			eof = ind.readForward(start, key, rid);
			
			runagain=(eof == 0 && (max == -1 || key <= max));
		}while(runagain);
	}
	else
	{
		bool runagain;
		
		//read in the first tuple
		eof = ind.readForward(start, key, rid);
		if(eof == 0) 
			eof=rf.read(rid, key, value);
				
		do{
			
			
			if(check==false || evalCond(key,value,conds))
			{
				printTuple(attr,key,value);
				count++;	
				
				
			}
			else{
				if(breakFalse)
					goto FINISH;
			}
			
			eof = ind.readForward(start, key, rid);
			
			if(eof == 0) 
				eof=rf.read(rid, key, value);
			
			runagain=(eof == 0 && (max == -1 || key <= max));
		}while(runagain);
		
	}
FINISH:
	if(attr == 4)
		printf("%d\n", count);
	
	return rc;
}
RC SqlEngine::select_index_and_rf(int attr, const string& table, const vector<SelCond>& conds, BTreeIndex& ind, RecordFile& rf)
{
	RC rc;

	return rc;
}
  
RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning
  BTreeIndex myBtreeIndex;

  RC     rc;
  int    key;     
  string value;
  int    count;
  int    diff;
  

  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }
  if((rc = myBtreeIndex.open(table + ".idx", 'r')) == 0)
  {
		rc= select_index(attr, table, cond, myBtreeIndex, rf);
		myBtreeIndex.close();
		return rc;
		
  }
		
  // scan the table file from the beginning
  rid.pid = rid.sid = 0;
  count = 0;
  while (rid < rf.endRid()) 
  {
    // read the tuple
    if ((rc = rf.read(rid, key, value)) < 0) {
      fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
      goto exit_select;
    }

    // check the conditions on the tuple
    if(evalCond(key, value, cond) == false)
		goto next_tuple;

    // the condition is met for the tuple. 
    // increase matching tuple counter
    count++;

    // print the tuple
     
    printTuple(attr,key, value);

    // move to the next tuple
    next_tuple:
    ++rid;
  }

  // print matching tuple count if "select count(*)"
  if (attr == 4) {
    fprintf(stdout, "%d\n", count);
  }
  rc = 0;

  // close the table file and return
  exit_select:
  rf.close();
  
  return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
  /* your code here */
  string line;
  ifstream myfile;
  RecordFile rf;
  BTreeIndex myBtreeIndex;
  RC rc;
  RecordId rid;
  
  myfile.open(loadfile.c_str(), ifstream::in);

  if (myfile.is_open())
  {
    if ((rc = rf.open(table + ".tbl", 'w')) < 0) {
      fprintf(stderr, "Error writing to table %s \n", table.c_str());
      return rc;
    }
    if(index==true && (rc = myBtreeIndex.open(table + ".idx", 'w')) < 0)
	{
			fprintf(stderr, "Error writing to index %s \n", table.c_str());
			return rc;
	}
    
    while ( myfile.good() )
    {
      int key;
	  string value;
	  
	  //get a line from the file
      getline(myfile,line);
      
      //parse the line from a file
      if((rc=parseLoadLine(line, key, value)) < 0)
      {
	    fprintf(stderr, "WARNING: could not parse record\n");
	  }
      
      //append the line to the record
      //if it fails, return.
      if(rc == 0 && (rc=rf.append(key, value, rid)) < 0)
	  {
		  fprintf(stderr, "ERROR: cannot write to record\n");
		  return rc;
	  }
     
     
      //if we are also creating an index 
      if(index == true && rc == 0 &&
		(rc=myBtreeIndex.insert(key, rid)) < 0){
		  fprintf(stderr, "ERROR: cannot write to index\n");
		  return rc;
	  }
	  
	  //myBtreeIndex.test();
	  
	
      
    }
    
		
    myfile.close();
    
    //if(index==true)
	//	myBtreeIndex.printTree();
    
		
    if ((rc = rf.close()) < 0) {
      fprintf(stderr, "ERR0R: cannot close table %s \n", table.c_str());
      return rc;
    }
    	
    if(index==true && 
       (rc=myBtreeIndex.close()) < 0){
	  fprintf(stderr, "ERROR: cannot close  index \n");
      return rc;
	}
    
  }
  else{
	  fprintf(stderr, "ERROR: File %s does not exist or cannot be opened.\n", loadfile.c_str());
	  return RC_FILE_OPEN_FAILED;
  }
  return rc;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
